package kbpost

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"mall/common/errorx"
	"mall/common/moneyx"
	"mall/common/utils/pointy"
	"mall/service/backend/api/internal/svc"
	"mall/service/backend/api/internal/types"
	"mall/service/backend/common/entity"
	"mall/service/blog/rpc/blogclient"

	"github.com/sirupsen/logrus"
	"github.com/xuri/excelize/v2"
	"github.com/zeromicro/go-zero/core/logx"
)

type DownloadAsExcelLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewDownloadAsExcelLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DownloadAsExcelLogic {
	return &DownloadAsExcelLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *DownloadAsExcelLogic) DownloadAsExcel(req *types.DownloadKBPostReq) (resp *types.BaseDataInfo, err error) {
	kbBrandEntityName := entity.ExcelEntityNames.KbBrand
	kbBrandSettingMap, hasKbBrandSettingMap := entity.ExcelEntityPreloadMap[kbBrandEntityName]
	if !hasKbBrandSettingMap {
		return
	}

	colSettingMap := make(map[string]*entity.ColumnSetting)
	for k, s := range kbBrandSettingMap {
		colSettingMap[k] = s
	}

	var buf bytes.Buffer
	var query map[string]any
	var mustConditions []map[string]any
	mustConditions = append(mustConditions, map[string]any{
		"term": map[string]any{
			"Sections.Slug": "perfumes",
		},
	})

	switch strings.TrimSpace(req.Filter) {
	case "nose":
		mustConditions = append(mustConditions, map[string]any{
			"term": map[string]any{
				"Nose.Id": req.Id,
			},
		})
	default:
		mustConditions = append(mustConditions, map[string]any{
			"term": map[string]any{
				"Brand.Id": req.Id,
			},
		})
	}

	query = map[string]any{
		"query": map[string]any{
			"bool": map[string]any{
				"must": mustConditions,
			},
		},
		"_source": []string{"Id", "Title", "Slug",
			"Relations.Id", "Relations.Title", "Relations.Slug", "Relations.Section", "Relations.GroupId",
			"Brand.Id", "Brand.Title", "Brand.Slug", "Nose.Id", "Nose.Title", "Nose.Slug", "Group.Id", "Group.Title", "Group.Slug",
			"Note.Id", "Note.Title", "Note.Slug", "Note.GroupId", "Year", "Rating",
		},
		"size": int(500),
	}
	if err := json.NewEncoder(&buf).Encode(query); err != nil {
		logrus.Info(fmt.Sprintf("DownloadAsExcel Error encoding query: %+v", err))
		return nil, errorx.Error(errorx.ERROR_DEFAULT, "")
	}

	// Perform the search request.
	client := l.svcCtx.EsClient
	res, err := client.Search(
		client.Search.WithContext(l.ctx),
		client.Search.WithIndex("scentrobe-kb-entry"),
		client.Search.WithBody(&buf),
		client.Search.WithTrackTotalHits(true),
		client.Search.WithPretty(),
	)
	if err != nil {
		logrus.Info(fmt.Sprintf("Error getting response: %+v", err))
		return nil, errorx.Error(errorx.ERROR_ELASTIC_INIT_FAILED, "")
	}

	defer res.Body.Close()
	if res.IsError() {
		var e map[string]any
		if err = json.NewDecoder(res.Body).Decode(&e); err != nil {
			logrus.Info(fmt.Sprintf("Error parsing the response body: %+v", err))
		} else {
			// Print the response status and error information.
			logrus.Info(fmt.Sprintf(
				"[%s] %s: %s",
				res.Status(),
				e["error"].(map[string]any)["type"],
				e["error"].(map[string]any)["reason"],
			))
		}

		return nil, errorx.Error(errorx.ERROR_ELASTIC_DOCUMENT_FAILED, "")
	}

	// logrus.Info(fmt.Sprintf("DownloadAsExcel res: %+v", res))

	var r map[string]any
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		logrus.Info(fmt.Sprintf("Error parsing the response body: %+v", err))
		return nil, errorx.Error(errorx.ERROR_ELASTIC_DOCUMENT_FAILED, "")
	}

	var total int64
	total = int64(r["hits"].(map[string]any)["total"].(map[string]any)["value"].(float64))

	if req.Diagnose == 1 {
		var meta []*blogclient.MetaInfo
		meta = append(meta, &blogclient.MetaInfo{
			Id:    pointy.GetPointer(uint64(req.Id)),
			Key:   pointy.GetPointer("_perfume_count"),
			Value: pointy.GetPointer(strconv.FormatInt(total, 10)),
		})
		_, err = l.svcCtx.BlogRpc.SetKBPostMeta(l.ctx, &blogclient.SetKBPostMetaReq{
			Meta: meta,
		})
		if err != nil {
			logrus.Info(fmt.Sprintf("DownloadAsExcel BackendSetKBPostMeta err: %+v", err))
			return &types.BaseDataInfo{
				Success: true,
				Msg:     "操作失败",
			}, nil
		}

		return &types.BaseDataInfo{
			Success: true,
			Msg:     "操作成功",
		}, nil
	}

	if total == 0 {
		return nil, errorx.Error(errorx.ERROR_KBPOST_ID_INVALID, "")
	}

	ef := excelize.NewFile()
	for _, setting := range colSettingMap {
		// Excel列号从1开始；行号从2开始，因为第1行要显示列名
		axis, err := excelize.CoordinatesToCellName(setting.Index, 1)
		if err != nil {
			logrus.Info(fmt.Sprintf("DownloadAsExcel CoordinatesToCellName err: %+v", err))
			return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
		}

		// 将数据写入默认工作表Sheet1中axis坐标处的单元格
		err = ef.SetCellValue("Sheet1", axis, setting.Head)
		if err != nil {
			logrus.Info(fmt.Sprintf("DownloadAsExcel SetCellValue err: %+v", err))
			return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
		}
	}

	for idx, hit := range r["hits"].(map[string]any)["hits"].([]any) {
		item := hit.(map[string]any)["_source"].(map[string]any)
		noteMap := make(map[string][]string)
		if _, ok := item["Note"]; ok {
			for _, r := range item["Note"].([]any) {
				relation := r.(map[string]any)
				groupId := int(relation["GroupId"].(float64))
				switch groupId {
				case 1:
					noteMap["TopNote"] = append(noteMap["TopNote"], relation["Title"].(string))
				case 2:
					noteMap["HeartNote"] = append(noteMap["HeartNote"], relation["Title"].(string))
				case 3:
					noteMap["BaseNote"] = append(noteMap["BaseNote"], relation["Title"].(string))
				}
			}
		}
		for col, setting := range colSettingMap {
			switch col {
			case "Nose", "Group":
				if _, ok := item[setting.Field]; ok {
					var vals []string
					for _, r := range item[setting.Field].([]any) {
						vals = append(vals, r.(map[string]any)[setting.SubField].(string))
					}

					axis, err := excelize.CoordinatesToCellName(setting.Index, idx+2)
					if err != nil {
						return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
					}

					err = ef.SetCellValue("Sheet1", axis, strings.Join(vals, ","))
					if err != nil {
						return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
					}
				}
			case "TopNote", "HeartNote", "BaseNote":
				if notes, ok := noteMap[col]; ok && len(notes) > 0 {
					axis, err := excelize.CoordinatesToCellName(setting.Index, idx+2)
					if err != nil {
						return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
					}

					err = ef.SetCellValue("Sheet1", axis, strings.Join(notes, ","))
					if err != nil {
						return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
					}
				}
			case "Id", "Slug", "Title", "Year", "Rating":
				if val, ok := item[setting.Field]; ok {
					axis, err := excelize.CoordinatesToCellName(setting.Index, idx+2)
					if err != nil {
						return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
					}

					switch setting.Type {
					case "int":
						err = ef.SetCellValue("Sheet1", axis, int(val.(float64)))
						if err != nil {
							return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
						}
					case "float":
						err = ef.SetCellValue("Sheet1", axis, moneyx.ParseStar(val.(string), 1))
						if err != nil {
							return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
						}
					case "string":
						err = ef.SetCellValue("Sheet1", axis, val.(string))
						if err != nil {
							return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
						}
					}
				}
			}
		}
	}

	now := time.Now()
	dirName := fmt.Sprintf("%s/%s", l.svcCtx.Config.NfsConf.PublicStorePath, now.Format("200601"))
	_, err = os.Stat(dirName)
	if err != nil {
		if os.IsNotExist(err) {
			if err = os.MkdirAll(dirName, 0775); err != nil {
				return nil, err
			}
		} else {
			return nil, err
		}
	}

	fileName := fmt.Sprintf("%s/brand_%d_kbposts_%d.xlsx", dirName, req.Id, now.Unix())
	if err = ef.SaveAs(fileName); err != nil {
		logrus.Info(fmt.Sprintf("DownloadAsExcel SaveAs err: %+v", err))
		return nil, errorx.Error(errorx.ERROR_EXCEL_WRITE_FAILED, "")
	}

	fileName = strings.Replace(fileName, filepath.Dir(filepath.Dir(l.svcCtx.Config.NfsConf.PublicStorePath)), l.svcCtx.Config.NfsConf.ProxyUrl, -1)

	return &types.BaseDataInfo{
		Success: true,
		Msg:     "操作成功",
		Data:    fileName,
	}, nil
}
